// Package mysql
// Author liuzhiyong
// Date 2023/11/17
// Description 库存 持久层
package mysql

import (
	"context"
	"fmt"
	"gitee.com/liu_zy_developer/go_stock_service/dao/redis"
	"gitee.com/liu_zy_developer/go_stock_service/errno"
	"gitee.com/liu_zy_developer/go_stock_service/model"
	"gorm.io/gorm"
	"gorm.io/gorm/clause"
)

// GetStockByGoodsId 根据商品id查询库存数组
func GetStockByGoodsId(ctx context.Context, goodsId int64) (*model.Stock, error) {
	var data model.Stock
	err := db.WithContext(ctx). // 绑定上下文
					Model(&model.Stock{}).          // 指定表明
					Where("goods_id = ?", goodsId). // 查询条件
					First(&data).                   // 将查询的结果扫描到结构体中, Find扫描到切片, Scan扫描单条数据到结构体, First返回切片的第一条
					Error
	fmt.Printf("data:%v\n", data)
	// 如果查询出错且不是空数据的错
	if err != nil && err != gorm.ErrRecordNotFound {
		return nil, errno.ErrQueryFailed
	}
	return &data, nil
}

// ReduceStock 扣减库存 带事务
//func ReduceStock(ctx context.Context, goodsId int64, num int64) (*model.Stock, error) {
//	// 1. 查询库存
//	var data model.Stock
//
//	// 自动事务
//	db.Transaction(func(tx *gorm.DB) error {
//		// 悲观锁添加 for update
//		err := tx.WithContext(ctx).Clauses(clause.Locking{Strength: "UPDATE"}).Model(&model.Stock{}).Select("goods_id = ?", goodsId).First(data).Error
//		if err != nil {
//			// 返回错误事务回滚
//			return err
//		}
//		// 2. 校验库存
//		if data.Num-num < 0 {
//			// 库存不足, 返回异常事务回滚
//			return errno.ErrUnderstock
//		}
//		// 3. 扣减库存
//		data.Num -= num
//		// 4. 保存库存
//		db.WithContext(ctx).Model(&model.Stock{}).Where("id = ?", data.ID).Update("num", data.Num)
//		//tx.WithContext(ctx).Save(&data)
//
//		// 返回nil, 提交事务
//		return nil
//	})
//
//	return &data, nil
//}

// ReduceStock 扣减库存 乐观锁版本
// func ReduceStock(ctx context.Context, goodsId, num int64) (*model.Stock, error) {
// 	// 1. 查询现有库存
// 	var (
// 		data      model.Stock
// 		retry     = 0
// 		isSuccess = false
// 	)
// 	for !isSuccess && retry < 20 {
// 		// 开启事务
// 		err := db.WithContext(ctx).
// 			Model(&model.Stock{}).
// 			Where("goods_id = ?", goodsId). // version 1
// 			First(&data).Error
// 		if err != nil {
// 			return nil, err
// 		}
// 		// 2. 校验；现有库存数>0 且 大于等于num
// 		if data.Num-num < 0 {
// 			return nil, errno.ErrUnderstock
// 		}
// 		// 3. 扣减
// 		data.Num -= num
// 		// 保存
// 		// err = tx.WithContext(ctx).
// 		// 	Save(&data).Error // save更新所有字段！
// 		// err = db.WithContext(ctx).
// 		// 	Model(&data). // 有主键
// 		// 	Update("num", data.Num).
// 		// 	Error
// 		// gorm where条件查询出数据并且做update操作，查询不到数据也不会报错。
// 		n := db.WithContext(ctx).
// 			Model(&model.Stock{}).                                             // 指定表名
// 			Where("goods_id = ? and version = ?", data.GoodsId, data.Version). // 从查找数据到更新数据这一段时间内，有人修改了数据那么version就不等于我之前查到的那个值
// 			Updates(map[string]interface{}{
// 				"goods_id": data.GoodsId,
// 				"num":      data.Num,
// 				"version":  data.Version + 1,
// 			}).RowsAffected // updates操作需要判断数据库返回的受影响的行数来判断
// 		if n < 1 {
// 			fmt.Printf("update err:%v\n", err)
// 			retry++ // 更新失败就重试
// 			continue
// 		}
// 		// 成功了
// 		isSuccess = true
// 		break
// 	}
// 	if !isSuccess {
// 		return nil, errno.ErrReducestockFailed
// 	}
// 	return &data, nil
// }

// ReduceStock 扣减库存,分布式锁版本
func ReduceStock(ctx context.Context, goodsId int64, num int64) (*model.Stock, error) {
	// 1. 查询库存
	var data model.Stock
	// 创建key
	mutename := fmt.Sprintf("xxx_stock_%d", goodsId)
	// 创建锁
	mutex := redis.Rs.NewMutex(mutename)
	// 获取锁
	if err := mutex.Lock(); err != nil {
		// 获取锁失败
		return nil, errno.ErrReducestockFailed
	}
	// 最后释放锁
	defer mutex.Unlock()
	// 自动事务
	db.Transaction(func(tx *gorm.DB) error {
		// 悲观锁添加 for update
		err := tx.WithContext(ctx).Clauses(clause.Locking{Strength: "UPDATE"}).Model(&model.Stock{}).Select("goods_id = ?", goodsId).First(data).Error
		if err != nil {
			// 返回错误事务回滚
			return err
		}
		// 2. 校验库存
		if data.Num-num < 0 {
			// 库存不足, 返回异常事务回滚
			return errno.ErrUnderstock
		}
		// 3. 扣减库存
		data.Num -= num
		// 4. 保存库存
		db.WithContext(ctx).Model(&model.Stock{}).Where("id = ?", data.ID).Update("num", data.Num)
		//tx.WithContext(ctx).Save(&data)

		// 返回nil, 提交事务
		return nil
	})

	return &data, nil
}
